

class Compiler {
    constructor(vm) {
        this.vm = vm
        this.el = vm.$el
        this.compile(this.el)  //加载这个类就调用这个方法
    }

    //编译模板，处理文本节点和元素节点
    compile (el) {
        let childNodes = el.childNodes  //元素的子节点，伪数组
        Array.from(childNodes).forEach(node => {
            if(this.isTextNode(node)) {
                //文本节点
                this.compileText(node)
            }else if(this.isElementNode(node)){
                //元素节点
                this.compileElement(node)
            }

            //判断node节点，是否有子节点，有的话递归调用
            if(node.childNodes && node.childNodes.length) {
                this.compile(node)
            }
        })
    }

    //编译元素节点，处理指令
    compileElement(node) {
        //node.attributes 获取元素节点的属性
        Array.from(node.attributes).forEach(attr => {
            let attrName = attr.name //熟悉的名字
            //截取指令v-text: text
            let key = attr.value //获取指令对应的变量
            if(this.isDirective(attrName)) {
                if(attrName.slice(0, 5) === 'v-on:') {
                   this.update(node, key, 'on', attrName.slice(5))
                }else {
                    attrName = attrName.slice(2)  //从第三个开始截取后面的值
                    this.update(node, key, attrName)
                }
            }
        })
    }
    

    //拼接指令
    update (node, key, attrName, eventName) {
       if(attrName !== 'on') {
          let updateFn = this[attrName + 'Updater']      //拼接处理对应指令的方法
          updateFn && updateFn.call(this, node, this.vm[key], key)
       }else {
         let updateFn = this[attrName + 'Updater']      //拼接处理对应指令的方法
         updateFn && updateFn.call(this, node, this.vm.$options['methods'][key], eventName)
       }
    }
  
    /**
     * 处理对应的指令封装成对应的方法好处是update()自动找到对应的方法解析，所以我们只需要关注处理指令的方法就行了，这样的话不管添加什么指令都直接写方法，不需要再在别的地方去调用，很方便
     * @param {*} node   节点
     * @param {*} value  变量对应的值 
     */

    //处理v-text
    textUpdater(node, value, key) {
        node.innerText = value

        new Watcher(this.vm, key, (newValue) => {
            node.innerText = newValue
        })
    }
    //处理v-model
    modelUpdater(node, value, key) {
        node.value = value
        new Watcher(this.vm, key, (newValue) => {
            node.value = newValue
        })


        node.addEventListener('input', () => {
            this.vm[key] = node.value
        })
    }
    

    //处理v-html
    htmlUpdater(node, value, key) {
        node.innerHTML = value
        new Watcher(this.vm, key, (newValue) => {
           node.innerHTML = newValue
        })
    }


    //处理v-on
    onUpdater (node, eventName, key) {
        node.addEventListener(key, eventName)
    }


    //编译文本节点，处理插值表达式
    compileText (node) {
        /**
         * {{ message }}
         * /\{\{.+?\}\}/  匹配花括号里面的值， \转义,  .匹配单个字符，+多个，?非贪婪模式，尽早完成  
         * /\{\{(.+?)\}\}/   ()分组的含义，匹配到分组的内容，这里就是匹配到变量名
         */
       
        let reg = /\{\{(.+?)\}\}/    //匹配变量名字
        let value = node.textContent // 获取文本内容
        if(reg.test(value)) {
            //是否匹配到正则，
            let key = RegExp.$1.trim()
            node.textContent = value.replace(reg, this.vm[key])
            //创建Watcher
            new Watcher(this.vm, key, (newValue) => {
                node.textContent = newValue
            })
        }
    }

    //判断元素属性是否是指令
    isDirective (attrName) {
        return attrName.startsWith('v-')   //指令都是以v-开头的
    }

    //判断节点属性是否是文本  nodeType: 节点类型， nodeValue：节点的值
    isTextNode(node) {
        return node.nodeType === 3
    }

    //判断节点是否是元素节点
    isElementNode(node) {
        return node.nodeType === 1
    }
}


